﻿using System;
using System.Collections.Generic;
using DependencyAnalysis.DataModel;

namespace DependencyAnalysis.Internal
{
    public class PseudoStack
    {
        public static readonly ICollection<OperationCode> ConsumingOpCodes = new HashSet<OperationCode>
            { OperationCode.Stloc, OperationCode.Stloc_0, OperationCode.Stloc_1, OperationCode.Stloc_2, OperationCode.Stloc_3, OperationCode.Stloc_S,
             OperationCode.Stfld, 
            OperationCode.Stsfld,OperationCode.And, OperationCode.Or, OperationCode.Mul, OperationCode.Mul_Ovf,
            OperationCode.Div, OperationCode.Mul_Ovf_Un, OperationCode.Div_Un, OperationCode.Add, OperationCode.Add_Ovf, OperationCode.Add_Ovf_Un, OperationCode.Sub, OperationCode.Sub_Ovf, OperationCode.Sub_Ovf_Un,
            OperationCode.Xor, OperationCode.Pop};

        public static readonly ICollection<OperationCode> ProducingOpCodes = new HashSet<OperationCode>
        {OperationCode.Ldfld,OperationCode.Ldflda, OperationCode.Ldsfld, OperationCode.Ldsflda, OperationCode.Ldtoken,OperationCode.Ldftn,
            OperationCode.Ldloc, OperationCode.Ldloc_0, OperationCode.Ldloc_1, OperationCode.Ldloc_2, OperationCode.Ldloc_3, OperationCode.Ldloc_S,OperationCode.Ldloca,OperationCode.Ldloca_S, 
            OperationCode.Ldarg, OperationCode.Ldarg_0, OperationCode.Ldarg_1, OperationCode.Ldarg_2, OperationCode.Ldarg_3, OperationCode.Ldarg_S, OperationCode.Ldarga,
            OperationCode.Ldc_I4,OperationCode.Ldc_I4_0,OperationCode.Ldc_I4_1,OperationCode.Ldc_I4_2,OperationCode.Ldc_I4_3,OperationCode.Ldc_I4_4,OperationCode.Ldc_I4_5,
            OperationCode.Ldc_I4_6,OperationCode.Ldc_I4_7,OperationCode.Ldc_I4_8,OperationCode.Ldc_I4_M1,OperationCode.Ldc_I4_S,OperationCode.Ldc_I8,OperationCode.Ldc_R4,
            OperationCode.Ldc_R8,OperationCode.Ldind_I,OperationCode.Ldind_I1, OperationCode.Ldind_I2, OperationCode.Ldind_I4, OperationCode.Ldind_I8,OperationCode.Ldind_R4,
            OperationCode.Ldind_R8, OperationCode.Ldind_Ref, OperationCode.Ldind_U1, OperationCode.Ldind_U2, OperationCode.Ldind_U4, OperationCode.Ldlen, OperationCode.Ldstr,
            OperationCode.Ldnull, OperationCode.Ldobj, OperationCode.Ldvirtftn, OperationCode.Newarr, OperationCode.Newobj
        };

        public static readonly ICollection<OperationCode> LoadElementOpCodes = new HashSet<OperationCode>
            { OperationCode.Ldelem, OperationCode.Ldelem_I, OperationCode.Ldelem_I1, OperationCode.Ldelem_I2, OperationCode.Ldelem_I4, OperationCode.Ldelem_I8,
             OperationCode.Ldelem_R4, OperationCode.Ldelem_R8, OperationCode.Ldelem_U1, OperationCode.Ldelem_U2, OperationCode.Ldelem_U4, OperationCode.Ldelema,
            OperationCode.Ldelem_Ref};

        public static readonly ICollection<OperationCode> StoreElementOpCodes = new HashSet<OperationCode>
            { OperationCode.Stelem, OperationCode.Stelem_I, OperationCode.Stelem_I1, OperationCode.Stelem_I2, OperationCode.Stelem_I4, OperationCode.Stelem_I8,
             OperationCode.Stelem_R4, OperationCode.Stelem_R8,OperationCode.Stelem_Ref };

        public IList<Instruction> Instructions { get; set; }

        public Instruction TrackCallTarget(Instruction call)
        {
            var startIndex = Instructions.IndexOf(call);
            if (startIndex < 0)
            {
                throw new ArgumentException();
            }
            int reduceStack;
            if (InstructionCache.MethodOpCodes.Contains(call.OpCode))
            {
                var calledMember = (MemberDescriptor)call.Operand;

                if (calledMember.IsStatic)
                {
                    return call;
                }
                reduceStack = calledMember.ParameterCount;
                if (!calledMember.IsStatic)
                {
                    reduceStack++;
                }
            }
            else if (StoreElementOpCodes.Contains(call.OpCode))
            {
                reduceStack = 3;
            }
            else if (InstructionCache.ConversionOpCodes.Contains(call.OpCode))
            {
                reduceStack = 0;
            }
            else
            {
                throw new ArgumentException();
            }
            int currentIndex = startIndex - 1;
            while (reduceStack > 0 && currentIndex > -1)
            {
                var currentInstruction = Instructions[currentIndex];
                if (ConsumingOpCodes.Contains(currentInstruction.OpCode))
                {
                    reduceStack++;
                }
                else if (ProducingOpCodes.Contains(currentInstruction.OpCode))
                {
                    reduceStack--;
                }
                else if (InstructionCache.MethodOpCodes.Contains(currentInstruction.OpCode))
                {
                    var member = (MemberDescriptor)currentInstruction.Operand;
                    reduceStack += member.ParameterCount;
                    if (!member.IsStatic)
                    {
                        reduceStack++;
                    }
                    if (member.ReturningType != null)
                    {
                        reduceStack--;
                    }
                }
                else if (LoadElementOpCodes.Contains(currentInstruction.OpCode))
                {
                    reduceStack++;
                }
                else if (StoreElementOpCodes.Contains(currentInstruction.OpCode))
                {
                    reduceStack += 3;
                }
                if (reduceStack == 0)
                {
                    break;
                }
                currentIndex--;
            }
            if (currentIndex > -1)
            {
                return Instructions[currentIndex];
            }
            return new Instruction { OpCode = OperationCode.Nop };
        }
    }
}
